/* This file is part of FireEscape.
 *
 * FireEscape is the intellectual work of Phillip Cheng & Allen Park.
 * All future releases or modifications of this program must include this
 * attribuation as according to the Creative Commons BY-SA license.
 *
 * FireEscape is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * any later version.
 *
 * FireEscape is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with FireEscape.  If not, see <http://www.gnu.org/licenses/>.
 *
 * All modifications to this program should include this original statement.
 *
 * If you modify, distribute, or use the program , please contact
 * fireescape@kloudvine.com
 * http://code.google.com/p/fireescape/
 *
 * If you are interested in using FireEscape for your structure, please contact us
 * for support.
 *
 * If you do use FireEscape, please consider donating to the project.
 */
package fireescape;

import java.applet.Applet;
import javax.swing.*;
import java.awt.*;
import java.awt.event.*;
import java.awt.geom.Point2D;
import java.io.BufferedReader;
import java.io.IOException;
import java.net.Socket;
import java.net.URL;
import java.net.URLConnection;
import java.util.ArrayList;
import java.util.logging.FileHandler;
import java.util.logging.Level;
import java.util.logging.Logger;
import java.util.logging.SimpleFormatter;
import java.util.logging.SocketHandler;
import java.io.InputStreamReader;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.MalformedURLException;
import java.net.SocketAddress;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;

/**
 * This is the main class for the GUI
 * @author chengP
 */
public class FireEscapeGUI extends Applet {

    public static final double scale = .9;
    public static final int xSize = 900;
    public static final int ySize = 600;
    //private final int xScale = 200;
    //private final int yScale = 200;
    //private static Image floorImage;
    private static boolean graphicsOn;
    public static boolean stairOutput;
    public static int stairNum;
    public static int floorNum;
    private FireEscape main;
    private Timer timer;
    //private Mouser mouser;
    private Keyboard keyboard;
    private MouseWheel mouseWheel;
    public static final boolean server = false;
    private BufferedReader kIn;

    /**
     * Initializes the GUI
     */
    @Override
    public void init() {
        /*try {
//TODO REMOVE THIS CODE COMMENT BELOW
            
            // get  server address
            URL kloudvine = new URL("http://www.kloudvine.com/onlineStatus.txt");
            URLConnection kloudvineConnect = kloudvine.openConnection();
            kIn = new BufferedReader(new InputStreamReader(kloudvineConnect.getInputStream()));
            String inputLine = kIn.readLine();
            if (inputLine.equals("offline")) {
                DialogApplet g = new DialogApplet("Thanks for participating! FireEscape Offline.");
                Component applet = g;
                g.init();
                JFrame frame = new JFrame("FireEscape");
                frame.getContentPane().add(applet);
                frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
                frame.pack();
                frame.setSize(300, 100);
                frame.show();
                Thread.sleep(5000);

                System.exit(0);
            }

            kIn.close();
        } catch (MalformedURLException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
            System.exit(-1);

        } catch (IOException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
            System.exit(-1);

        } catch (InterruptedException ex) {
            System.exit(-1);
        }*/
        //logging

        Logger logger = Logger.getLogger("FireEscapeLog");
        ArrayList<String> addresses = new ArrayList<String>();

        DialogApplet g = null;
        //applet dialog pane
        g = new DialogApplet("Connecting to kloudvine.com");
        Component applet = g;
        g.init();

        try {
            // Set System L&F
            UIManager.setLookAndFeel(
                    UIManager.getSystemLookAndFeelClassName());
        } catch (UnsupportedLookAndFeelException ex) {
        } catch (ClassNotFoundException ex) {
        } catch (InstantiationException ex) {
        } catch (IllegalAccessException ex) {
        }
        JFrame frame = new JFrame("FireEscape");

        frame.getContentPane().add(applet);
        frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
        frame.pack();
        frame.setSize(300, 100);
        frame.setBackground(Color.WHITE);
        double time = System.currentTimeMillis();
                    //TODO REMOVE THIS LINE BELOW
addresses.add("localhost");
        /*try {

            // get  server address
            URL kloudvine = new URL("http://www.kloudvine.com/loggingServers.txt");
            URLConnection kloudvineConnect = kloudvine.openConnection();
            kIn = new BufferedReader(new InputStreamReader(kloudvineConnect.getInputStream()));
            String inputLine;

            while ((inputLine = kIn.readLine()) != null) {
                addresses.add(inputLine);
            }


            kIn.close();
        } catch (MalformedURLException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
            frame.setVisible(true);
            g.setVisible(true);
        } catch (IOException ex) {
            System.err.println("Server is offline, trying next server");
            System.err.println("Please contact the FireEscape admin.");
            Logger.getLogger("FireEscapeLog").log(Level.SEVERE, null, ex);
            frame.setVisible(true);
            g.setVisible(true);

        } finally {
        }*/

        int i = 0;
        Boolean notFound = true;
        while (notFound) {
            try {

                //test log server and get working server
                InetAddress addr = InetAddress.getByName(addresses.get(i));
                int port = 5000;
                SocketAddress server = new InetSocketAddress(addr, port);
                // Create an unbound socket
                Socket clientSocket = new Socket();
                // This method will block no more than timeoutMs.
                // If the timeout occurs, SocketTimeoutException is thrown.
                int timeoutMs = 2000;   // 2 seconds
                clientSocket.connect(server, timeoutMs);
                notFound = false;
                clientSocket.close();

            } catch (SocketTimeoutException ex) {
                i++;
                System.err.println("Server is offline, trying next server");
                System.err.println("Please contact the FireEscape admin.");
                ex.printStackTrace();
                frame.setVisible(true);
                g.setVisible(true);


            } catch (UnknownHostException ex) {
                System.err.println("Kloudvine server offline");
                ex.printStackTrace();
                frame.setVisible(true);
                g.setVisible(true);
            } catch (ConnectException ex) {
                ex.printStackTrace();
                frame.setVisible(true);
                g.setVisible(true);
            } catch (IOException ex) {
                ex.printStackTrace();
                frame.setVisible(true);
                g.setVisible(true);


            } catch (IndexOutOfBoundsException ex) {
                System.err.println("Logging servers offline. Errors not logged.");
                System.err.println("Please contact the FireEscape admin.");
                ex.printStackTrace();

                i = 0;
            }
        }
        try {
            // This block configures the logger with handler and formatter
            SocketHandler sh = new SocketHandler(addresses.get(i), 5000);
            System.out.println(addresses.get(i));
            sh.setFormatter(new SimpleFormatter());
            Logger.getLogger("FireEscapeLog").addHandler(sh);
            Logger.getLogger("FireEscapeLog").setLevel(Level.INFO);
            // the following statement is used to log any messages
            Logger.getLogger("FireEscapeLog").log(Level.INFO, "Client Connected: " + InetAddress.getLocalHost().toString());
        } catch (SecurityException e) {
            e.printStackTrace();
            frame.setVisible(true);
            g.setVisible(true);

        } catch (IOException e) {
            frame.setVisible(true);
            g.setVisible(true);

            e.printStackTrace();
        }

        frame.dispose();

        //Set canvas size
        this.setSize(xSize + 150, ySize + 5);
        floorNum = 0;
        graphicsOn = true;
        //initFloorImage();
        //im = createImage(xSize, ySize);

        main = new FireEscape(xSize, ySize);
        main.start();

        ActionListener listener = new Timing();
        timer = new Timer(1, listener);

        timer.start();

        /*mouser = new Mouser();
        this.addMouseListener(mouser);*/

        keyboard = new Keyboard();
        this.addKeyListener(keyboard);

        mouseWheel = new MouseWheel();
        this.addMouseWheelListener(mouseWheel);
    }

    /*public void initFloorImage() {
    floorImage = createImage((int) (xSize * scale), (int) (ySize * scale));
    }*/
    /**
     * Updates the screen
     * @param g The Graphics object for the screen
     */
    @Override
    public void update(Graphics g) {
        Image im = createImage(xSize + 150, ySize + 5);
        paint((Graphics2D) im.getGraphics(), false);
        g.drawImage(im, 0, 0, null);
    }

    /**
     * Draws on the screen - only called once right after init() is called€
     * @param g The Graphics object used for drawing
     */
    @Override
    public void paint(Graphics g) {
        paint((Graphics2D) g, false);
    }

    /**
     * Draws on the screen
     * @param g The Graphics2D object used for drawing
     * @param routeOutput If true, then output routes. If false, then draw the screen.
     */
    public void paint(Graphics2D g, boolean routeOutput) {
        if (routeOutput) {
            Image[] floorImages = new Image[Map.getNumFloors()];
            for (int i = 0; i < floorImages.length; i++) {
                floorImages[i] = createImage((int) (xSize * FireEscapeGUI.scale), (int) (ySize * FireEscapeGUI.scale));
            }
            //Map.setUpMap(floorImages);
        } else if (stairOutput) {
            main.paint(g, stairNum);
        } else {
            main.paint(g, floorNum);
        }
    }

    /**
     * Scales the points using FireEscapeGUI.scale
     * @param points The points to be scaled
     * @return The scaled points
     */
    public static ArrayList<Point2D.Double> scale(ArrayList<Point2D.Double> points) {
        ArrayList<Point2D.Double> temp = new ArrayList<Point2D.Double>();
        for (Point2D.Double point : points) {
            temp.add(new Point2D.Double(point.getX() * FireEscapeGUI.scale, point.getY() * FireEscapeGUI.scale));
        }
        return temp;
    }

    /**
     * Returns if the first segment is on the second segment
     * @param testpoint1 The first point on the first segment
     * @param testpoint2 The second point on the first segment
     * @param endpoint1 The first point on the second segment
     * @param endpoint2 The second point on the second segment
     * @return True if the first segment is on the second segment, false if not
     */
    public static boolean segmentOnSegment(Point2D.Double testpoint1, Point2D.Double testpoint2, Point2D.Double endpoint1, Point2D.Double endpoint2) {
        return pointOnSegment(testpoint1, endpoint1, endpoint2) && pointOnSegment(testpoint2, endpoint1, endpoint2);
    }

    /**
     * Returns if the point is on the segment
     * @param testpoint The point
     * @param endpoint1 The first point on the segment
     * @param endpoint2 The second point on the segment
     * @return True if the point is on the segment, false if not
     */
    public static boolean pointOnSegment(Point2D.Double testpoint, Point2D.Double endpoint1, Point2D.Double endpoint2) {
        double lowerX = endpoint2.getX();
        double upperX = endpoint1.getX();
        double lowerY = endpoint2.getY();
        double upperY = endpoint1.getY();
        if (endpoint1.getX() < endpoint2.getX()) {
            lowerX = endpoint1.getX();
            upperX = endpoint2.getX();
        }
        if (endpoint1.getY() < endpoint2.getY()) {
            lowerY = endpoint1.getY();
            upperY = endpoint2.getY();
        }
        if (testpoint.getX() < lowerX || testpoint.getX() > upperX ||
                testpoint.getY() < lowerY || testpoint.getY() > upperY) {
            return false;
        }
        if ((testpoint.getX() == endpoint1.getX() && testpoint.getX() == endpoint2.getX()) ||
                testpoint.equals(endpoint1) || testpoint.equals(endpoint2)) {
            return true;
        }
        double slope1 = (endpoint1.getY() - testpoint.getY()) / (endpoint1.getX() - testpoint.getX());
        double slope2 = (testpoint.getY() - endpoint2.getY()) / (testpoint.getX() - endpoint2.getX());
        return slope1 == slope2;
    }

    public static double pointToLine(Point2D.Double testPoint, Point2D.Double point1, Point2D.Double point2) {
        double testX = testPoint.getX() - point1.getX();
        double testY = testPoint.getY() - point1.getY();
        double pointX = point2.getX() - point1.getX();
        double pointY = point2.getY() - point1.getY();
        double scalar = (testX * pointX + testY * pointY) / (pointX * pointX + pointY * pointY);
        double newX = scalar * pointX;
        double newY = scalar * pointY;
        return distance(testX, testY, newX, newY);
    }

    public static boolean inPerpendicularRange(Point2D.Double testPoint, Point2D.Double point1, Point2D.Double point2) {
        double testX = testPoint.getX() - point1.getX();
        double testY = testPoint.getY() - point1.getY();
        double pointX = point2.getX() - point1.getX();
        double pointY = point2.getY() - point1.getY();
        double scalar = (testX * pointX + testY * pointY) / (pointX * pointX + pointY * pointY);
        double newX = scalar * pointX;
        double newY = scalar * pointY;
        return pointOnSegment(new Point2D.Double(newX, newY), new Point2D.Double(0, 0), new Point2D.Double(pointX, pointY));
    }

    /**
     * Returns the distance between (x1, y1) and (x2, y2)
     * @param x1 The first x coordinate
     * @param y1 The first y coordinate
     * @param x2 The second x coordinate
     * @param y2 The second y coordinate
     * @return The distance between (x1, y1) and (x2, y2)
     */
    public static double distance(double x1, double y1, double x2, double y2) {
        double xDistance = x2 - x1;
        double yDistance = y2 - y1;
        return Math.sqrt(xDistance * xDistance + yDistance * yDistance);
    }

    /*public static Image getNewImage() {
    return createImage((int) (xSize * FireEscapeGUI.scale), (int) (ySize * FireEscapeGUI.scale));
    }*/

    /*public static Image copyFloorImage(Image im) {
    Graphics2D g = (Graphics2D) floorImage.getGraphics();
    g.drawImage(im, 0, 0, null);
    return floorImage;
    }*/
    /**
     * Controls the Timer
     * Runs FireEscape
     */
    public class Timing implements ActionListener {

        @Override
        public void actionPerformed(ActionEvent e) {
            main.run();
            if (graphicsOn) {
                repaint();
            }
        }
    }

    /*public class Mouser implements MouseListener {

    public void mouseClicked(MouseEvent e) {
    }

    public void mousePressed(MouseEvent e) {
    }

    public void mouseReleased(MouseEvent e) {
    }

    public void mouseEntered(MouseEvent e) {
    }

    public void mouseExited(MouseEvent e) {
    }
    }*/
    /**
     * Controls the keyboard
     * Press 'a' and 'd' to move the floors
     * Press 'g' to toggle graphics
     */
    public class Keyboard implements KeyListener {

        @Override
        public void keyTyped(KeyEvent e) {
            char key = e.getKeyChar();
            if (key == 'a') {
                if (!stairOutput) {
                    floorNum--;
                    floorNum %= Map.getNumFloors();
                    if (floorNum < 0) {
                        floorNum += Map.getNumFloors();
                    }
                } else {
                    stairNum--;
                    stairNum %= Map.getNumStaircases();
                    if (stairNum < 0) {
                        stairNum += Map.getNumStaircases();
                    }
                }
            } else if (key == 'd') {
                if (!stairOutput) {
                    floorNum++;
                    floorNum %= Map.getNumFloors();
                    if (floorNum < 0) {
                        floorNum += Map.getNumFloors();
                    }
                } else {
                    stairNum++;
                    stairNum %= Map.getNumStaircases();
                    if (stairNum < 0) {
                        stairNum += Map.getNumStaircases();
                    }
                }
            } else if (key == 'g') {
                graphicsOn = !graphicsOn;
            } else if (key == 's') {
                stairOutput = !stairOutput;
            }
        }

        public void keyPressed(KeyEvent e) {
        }

        public void keyReleased(KeyEvent e) {
        }
    }

    /**
     * Controls the mouse wheel
     * Controls the output level
     */
    public class MouseWheel implements MouseWheelListener {

        public void mouseWheelMoved(MouseWheelEvent e) {
            int wheelRotation = e.getWheelRotation();
            if (wheelRotation < 0) {
                if (GeneticAI.scrollVar != 0) {
                    GeneticAI.scrollVar--;
                }
            } else if (wheelRotation > 0) {
                GeneticAI.scrollVar++;
            }
        }
    }
}
